package org.xmlbinder;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;


/**
 * @author Noble Paul (noble.paul@gmail.com)
 *         Date: Aug 17, 2007
 *         Time: 2:12:42 PM
 */
public class XmlUnbinder {
    private Properties serializeProperties;
    private String rootNodeName;
    private Logger logger;
    private boolean ignoreWarnings = true;

    /**Sets the output format to be intended .
     */
    public XmlUnbinder setNeatFormat() {
        setSerializeProperty("http://xmlpull.org/v1/doc/properties.html#serializer-indentation", "\t");
        setSerializeProperty("http://xmlpull.org/v1/doc/properties.html#serializer-line-separator", NEW_LINE);
        return this;
    }

    /**To set a property on the underlying xpp serializer
     * @param name
     * @param val
     */
    public XmlUnbinder setSerializeProperty(String name, String val) {
        if (serializeProperties == null) {
            serializeProperties = new Properties();
        }
        serializeProperties.put(name, val);
        return this;
    }

    /**if the root Object class is not annotated with an {@link org.xmlbinder.Element </code>Element<code>} tag.
     * Or use it if you want the root node name to be different.
     * @param rootNodeName
     */
    public XmlUnbinder setRootNodeName(String rootNodeName) {
        this.rootNodeName = rootNodeName;
        return this;
    }

    /** Serialize an Object to an outputStream.
     * @param os
     * @param o   The object to be seraialized
     * @throws IOException
     */
    public void unbind(OutputStream os, Object o) throws IOException {
        doUnbind(getSerializer(os,null), getMeta(o), o, this);
    }

    /**Serialize an object to a character stream.
     * @param w
     * @param o  The object to be seraialized
     * @throws IOException
     */
    public void unbind(Writer w, Object o) throws IOException {
        doUnbind( getSerializer(null,w), getMeta(o) , o,this);
    }

    /**Serialize an Object to a StringWriter and returns an xml String
     * @param o The object to be seraialized
     * @return The xml String
     */
    public String unbind(Object o) {
        StringWriter sw = new StringWriter();
        try {
            doUnbind(getSerializer(null,sw), getMeta(o), o,this);
        } catch (IOException e) {
            //StringWriter does not throw IOException
            throw new RuntimeException(e);
        }
        return sw.toString();
    }

    private BeanInjector getMeta(Object o) {
        return XmlBinder.getBeanInjectorFromCache(o.getClass());
    }

    public boolean isIgnoreWarnings() {
        return ignoreWarnings;
    }

    public XmlUnbinder setIgnoreWarnings(boolean ignoreWarnings) {
        this.ignoreWarnings = ignoreWarnings;
        return this;
    }

    public Logger getLogger() {
        return logger;
    }

    public XmlUnbinder setLogger(Logger logger) {
        this.logger = logger;
        return this;
    }

    private void doUnbind(XmlSerializer ser,
                          BeanInjector injector, Object o, XmlUnbinder xub) throws IOException {
        String root = rootNodeName == null?injector.getName():rootNodeName;
        TagStack rootTag = new TagStack(root);
        rootTag.openTag(ser);
        injector.unbind(rootTag, o, ser, xub);
        rootTag.closeTag(ser);
    }

    private XmlSerializer getSerializer(OutputStream os, Writer w) throws IOException {
        try {
            XmlSerializer ser = XmlBinder.factory.newSerializer();
            if (serializeProperties != null && !serializeProperties.isEmpty()) {
                for (Map.Entry<Object, Object> e : serializeProperties.entrySet()) {
                    ser.setProperty((String) e.getKey(), e.getValue());
                }
            }
            if(w!= null){
                ser.setOutput(w);
            } else {
                ser.setOutput(os,"utf-8");
            }
            return ser;
        } catch (XmlPullParserException e) {
            throw new RuntimeException(e);// cannot happen
        }
    }

    static final String NEW_LINE = System.getProperty("line.separator");
}
